/**
 * GlobalTransactionManager. This is the manager of all the transaction. It has to be on a dedicated server
 * 
 * @author guillaumeR
 */

package gicom.transaction;

import gicom.PersistentObject;
import gicom.generated.transaction.GlobalTransactionManagerOperations;
import gicom.generated.transaction.GlobalTransactionManagerPOATie;
import gicom.generated.transaction.InvalidStampException;
import gicom.generated.transaction.InvalidStateException;
import gicom.generated.transaction.LocalTransactionManager;
import gicom.generated.transaction.State;
import gicom.generated.transaction.Vote;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.omg.CORBA.ORB;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.Servant;

public class GlobalTransactionManagerImpl extends PersistentObject implements GlobalTransactionManagerOperations {

    /**
     * Static stamp for all the transactions
     */
    private static int transactionNumber;

    /**
     * all the transactions
     */
    private Map<Integer, Transaction> transactions;

    /**
     * Logger
     */
    private static Logger logger = Logger.getLogger(GlobalTransactionManagerImpl.class.getName());

    /**
     * Constructor of the GlobalTransactionManager
     */
    public GlobalTransactionManagerImpl() {
	super("GlobalTM");
	setTransactionNumber(0);
	transactions = new HashMap<Integer, Transaction>();
	logger.info("GlobalTransactionManager started");
    }

    @Override
    public int begin() {
	// TODO : log info into a file
	setTransactionNumber(getTransactionNumber() + 1);
	this.transactions.put(getTransactionNumber(), new Transaction(getTransactionNumber()));
	return getTransactionNumber();
    }

    @Override
    public boolean commit(int id) throws InvalidStampException, InvalidStateException {
	// TODO : log info into a file
	logger.info("GTM COMMITING ...");
	if (this.transactions.containsKey(id)) {
	    if (this.transactions.get(id).getState().equals(State.INITIAL)) {

		// set the transaction state to PREPARE
		this.transactions.get(id).setState(State.PREPARE);

		// for all LTM, ask to prepare the transaction, first step of
		// the phase
		LocalTransactionManager localTransactionManager;
		List<LocalTransactionManager> ltmList = this.transactions.get(id).getLtms();
		Iterator<LocalTransactionManager> iter = ltmList.iterator();

		while (iter.hasNext()) {
		    localTransactionManager = (LocalTransactionManager) iter.next();

		    // if one of the LTM vote a rollback, then rollback the
		    // transaction

		    Vote votet = localTransactionManager.prepare(id);

		    if (votet == Vote.VOTEROLLBACK) {
			logger.info("GTM VoteRollback");
			this.transactions.get(id).setVoteState(Vote.VOTEROLLBACK);
			// do the rollback as the second commit phase ... so bad
			// :(
			this.rollback(id);

			return false;
		    }

		}

		// if here, means that every LTM vote commit. Then set the vote
		// state of the transaction to COMMIT
		this.transactions.get(id).setVoteState(Vote.VOTECOMMIT);

		// for all LTM, ask to commit. Here is the second commit phase.
		iter = ltmList.iterator();
		while (iter.hasNext()) {
		    localTransactionManager = (LocalTransactionManager) iter.next();

		    localTransactionManager.commit(id);
		}

		// set the sate of the transaction to COMMITED
		this.transactions.get(id).setState(State.COMMITED);

		return true;

	    } else {
		logger.info("GTM VoteRollback");
		this.transactions.get(id).setVoteState(Vote.VOTEROLLBACK);
		// do the rollback as the second commit phase ... so bad
		// :(
		this.rollback(id);
		throw new InvalidStateException("commit GlobalTM");
	    }
	} else {
	    logger.info("GTM VoteRollback");
	    this.transactions.get(id).setVoteState(Vote.VOTEROLLBACK);
	    // do the rollback as the second commit phase ... so bad
	    // :(
	    this.rollback(id);
	    throw new InvalidStampException();
	}

    }

    @Override
    public void enroll(int id, LocalTransactionManager ltm) throws InvalidStampException, InvalidStateException {
	// TODO : log info into a file
	if (this.transactions.containsKey(id))
	    if (this.transactions.get(id).getState().equals(State.INITIAL)) {
		logger.info("Enrolling LTM : " + ltm.toString());
		this.transactions.get(id).addLTM(ltm);
	    } else
		throw new InvalidStateException("enroll GlobalTM");
	else
	    throw new InvalidStampException();
    }

    @Override
    public State getCurrentState(int id) throws InvalidStampException {
	return this.transactions.get(id).getState();
    }

    @Override
    public void rollback(int id) throws InvalidStampException, InvalidStateException {
	// TODO : log info into a file
	if (this.transactions.containsKey(id)) {
	    if (this.transactions.get(id).getState().equals(State.PREPARE)) {
		this.transactions.get(id).setState(State.ROLLBACKED);

		LocalTransactionManager localTransactionManager;
		List<LocalTransactionManager> ltmList = this.transactions.get(id).getLtms();
		Iterator<LocalTransactionManager> iter = ltmList.iterator();

		while (iter.hasNext()) {
		    localTransactionManager = (LocalTransactionManager) iter.next();
		    localTransactionManager.rollback(id);
		}

	    } else {
		throw new InvalidStateException("rollback TM");
	    }
	} else {
	    throw new InvalidStampException();
	}

	return;
    }

    /**
     * set the transaction number
     * 
     * @param transactionNumber
     */
    private static void setTransactionNumber(int transactionNumber) {
	GlobalTransactionManagerImpl.transactionNumber = transactionNumber;
    }

    /**
     * 
     * @return the last transaction number
     */
    private static int getTransactionNumber() {
	return transactionNumber;
    }

    @Override
    public Servant createServant() {
	return new GlobalTransactionManagerPOATie(this);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
	// id
	this.id = in.readUTF();

	// transaction number
	setTransactionNumber(in.readInt());

	// transactions
	this.transactions = new HashMap<Integer, Transaction>();
	int count = in.readInt();
	for (int i = 0; i < count; i++) {
	    Transaction tr = (Transaction) in.readObject();
	    this.transactions.put(tr.getTransactionNumber(), tr);
	}

    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
	// id
	out.writeUTF(this.id);

	// transaction number
	out.writeInt(getTransactionNumber());

	// transactions
	out.writeInt(this.transactions.size());
	for (Transaction tr : this.transactions.values()) {
	    out.writeObject(tr);
	}

    }

    @Override
    public void doReload(ORB orb, POA poa) throws Exception {

    }

}
